<?php
namespace helper;

use idea\ap\ErrorException;

/**
 * 定义默认的脚本最大执行时间，默认2小时
 */
defined('TIME_LIMIT') || define('TIME_LIMIT', 7200);

/**
 * 设置脚本的最大执行时间
 */
set_time_limit(TIME_LIMIT);

/**
 * Script abstract class file
 * 所有脚本的基类
 * @author 宋欢 <iphper@yeah.net>
 * @version $Id: Script.php 1 2013-04-05 19:29:06Z huan.song $
 * @package helper
 * @since 1.0
 */
abstract class Script
{
	/**
	 * @var boolean 脚本是否可以开多个端口重复运行
	 */
	protected $_canRepeatRun = true;
	
	/**
	 * @var string|null 存储脚本运行的进程ID的文件
	 */
	protected $_PIDFile = null;
	
	/**
	 * 运行脚本
	 * @return void
	 */
	public function run()
	{
		$log = sprintf('Script PID "%d", flag "%s", task ', $this->getPID(), $this->getFlag());
		
		if (!$this->_canRepeatRun && $this->isRun()) {
			Log::warning($log . 'has already running', ErrorNo::ERROR_SCRIPT_RUN_ERR, __METHOD__);
			exit(ErrorNo::ERROR_SCRIPT_RUN_ERR);
		}
		
		Log::notice($log . 'start run', __METHOD__);
		$this->setPID();
		
		$this->_run();
		
		$this->delPID();
		Log::notice($log . 'finished running', __METHOD__);
		exit(ErrorNo::SUCCESS_NUM);
	}
	
	/**
	 * 判断脚本是否已经在运行
	 * @return boolean
	 */
	public function isRun()
	{
		$pid = $this->getPID();
		if ($pid > 0 && posix_kill($pid, 0)) {
			return true;
		}
		
		return false;
	}
	
	/**
	 * 获取脚本运行的进程ID
	 * @return integer
	 */
	public function getPID()
	{
		$PIDFile = $this->getPIDFile();
		if (!is_file($PIDFile)) {
			return 0;
		}
		
		if (!($pid = (int) file_get_contents($PIDFile))) {
			throw new ErrorException(sprintf(
				'Script get PID failed, PIDFile is empty or is not readable, PIDFile "%s"', $this->getPIDFile()
			));
		}
		
		return $pid;
	}
	
	/**
	 * 设置脚本运行的进程ID
	 * @return boolean
	 */
	public function setPID()
	{
		$pid = posix_getpid();
		if (file_put_contents($this->getPIDFile(), $pid) <= 0) {
			throw new ErrorException(sprintf(
				'Script create PID file failed, PIDFile "%s", PID "%d"', $this->getPIDFile(), $pid
			));
		}
		
		return true;
	}
	
	/**
	 * 删除存储脚本运行的进程ID的文件
	 * @return boolean
	 */
	public function delPID()
	{
		if (!unlink($this->getPIDFile())) {
			throw new ErrorException(sprintf(
				'Script unlink PID file failed, PIDFile "%s"', $this->getPIDFile()
			));
		}
		
		return true;
	}
	
	/**
	 * 获取存储脚本运行的进程ID的文件
	 * @return string
	 */
	public function getPIDFile()
	{
		if ($this->_PIDFile === null) {
			$this->_PIDFile = DIR_LOG_APP . DS . $this->getFlag();
		}
		
		return $this->_PIDFile;
	}
	
	/**
	 * 具体脚本运行实现逻辑
	 * @return void
	 */
	protected abstract function _run()
	{
	}
	
	/**
	 * 该脚本的唯一标示，多为脚本名称
	 * @return string
	 */
	public function getFlag()
	{
		return get_class($this);
	}
}
